package mirrormonkey.util.annotations.parsing.classlevel;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationIR;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.MemberIR;
import mirrormonkey.util.annotations.parsing.classlevel.FlatMemberlessClassIRManagementTest.TestContainer.DeclareDefaultIR;
import mirrormonkey.util.annotations.parsing.classlevel.FlatMemberlessClassIRManagementTest.TestContainer.DeclareExtendingIR;
import mirrormonkey.util.annotations.parsing.classlevel.FlatMemberlessClassIRManagementTest.TestContainer.DeclareNoIR;
import mirrormonkey.util.annotations.parsing.classlevel.FlatMemberlessClassIRManagementTest.TestContainer.DeclareOverride;
import mirrormonkey.util.annotations.parsing.classlevel.FlatMemberlessClassIRManagementTest.TestContainer.DeclareOverrideAndExtendingIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning the instantiation of class-level IRs where
 * classes don't have ancestors or membersById.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class FlatMemberlessClassIRManagementTest {

	/**
	 * <tt>AnnotationParser</tt> under test
	 */
	public static AnnotationParser parser;

	/**
	 * All test classes contained in <tt>TestContainer</tt> are parsed during
	 * <tt>setUpBeforeClass</tt>. Test cases that rely on these classes will
	 * retrieve the results to check from this <tt>Map</tt>.
	 */
	public static Map<String, ClassIR> mappedResults;

	/**
	 * Will provoke an exception when parsing because class level IRs must
	 * inherit from <tt>ClassIR</tt>.
	 */
	@IRClass(AnnotationIR.class)
	public static class DeclareAnnotationIR {
	}

	/**
	 * Will provoke an exception when parsing because class level IRs must
	 * inherit from <tt>ClassIR</tt>.
	 */
	@IRClass(MemberIR.class)
	public static class DeclareMemberCollectingIR {
	}

	/**
	 * Will provoke an exception when parsing because class level IRs must
	 * inherit from <tt>ClassIR</tt>.
	 */
	@IRClass(Object.class)
	public static class DeclareObject {
	}

	/**
	 * This is a valid IR class that can be used for classes. Since it declares
	 * constructors for both <tt>AnnotationParser</tt> and <tt>ClassIR</tt>, it
	 * will be created by the parser if it is either the first declared IR for a
	 * class or the previous IR was a <tt>ClassIR</tt>.<br>
	 */
	public static class ExtendingClassIR extends ClassIR {
		public ExtendingClassIR(AnnotationParser parser, Class<?> forClass) {
			super(parser, forClass);
		}

		public ExtendingClassIR(ClassIR previous, Class<?> forClass) {
			super(previous, forClass);
		}
	}

	/**
	 * Will provoke an exception when used as default class IR as it does not
	 * provide a constructor with <tt>AnnotationParser</tt>.
	 */
	public static class InvalidFirstExtendingIR extends ClassIR {
		public InvalidFirstExtendingIR(ClassIR previous, Class<?> forClass) {
			super(previous, forClass);
		}

	}

	/**
	 * Can be used as default <tt>ClassIR</tt>, but will provoke an exception
	 * when used if there is already an IR present that is not an instance of
	 * exactly this class because it is missing a constructor that takes a
	 * <tt>ClassIR</tt> as parameter.
	 */
	public static class InvalidSecondExtendingIR extends ClassIR {
		public InvalidSecondExtendingIR(AnnotationParser parser,
				Class<?> forClass) {
			super(parser, forClass);
		}

		public InvalidSecondExtendingIR(InvalidSecondExtendingIR previous,
				Class<?> forClass) {
			super(previous, forClass);
		}

	}

	/**
	 * Will provoke an exception when used after an <tt>AnnotationOverride</tt>,
	 * but should not provoke an exception when used normally.
	 */
	@AnnotationOverride
	@IRClass(InvalidFirstExtendingIR.class)
	public static class DeclareInvalidFirstOverride {
	}

	/**
	 * Will provoke an exception when used after an <tt>AnnotationOverride</tt>,
	 * but should not provoke an exception when used normally.
	 */
	@IRClass(InvalidFirstExtendingIR.class)
	public static class DeclareInvalidFirstNoOverride {
	}

	/**
	 * Will provoke an exception when used normally, but will not provoke an
	 * exception when used after an <tt>AnnotationOverride</tt>.
	 */
	@AnnotationOverride
	@IRClass(InvalidSecondExtendingIR.class)
	public static class DeclareInvalidNormalOverride {
	}

	/**
	 * Will provoke an exception when used normally, but will not provoke an
	 * exception when used after an <tt>AnnotationOverride</tt>.
	 */
	@IRClass(InvalidSecondExtendingIR.class)
	public static class DeclareInvalidNormalNoOverride {
	}

	/**
	 * Contains various class definitions that should not provoke an exception
	 * when parsed
	 */
	public static class TestContainer {
		public static class DeclareNoIR {
		}

		@IRClass(ClassIR.class)
		public static class DeclareDefaultIR {
		}

		@IRClass(ExtendingClassIR.class)
		public static class DeclareExtendingIR {
		}

		@AnnotationOverride
		public static class DeclareOverride {
		}

		@AnnotationOverride
		@IRClass(ExtendingClassIR.class)
		public static class DeclareOverrideAndExtendingIR {
		}

	}

	/**
	 * Parses all test classes that should not provoke any exceptions and stores
	 * the resulting IRs.
	 */
	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
		mappedResults = new HashMap<String, ClassIR>();

		for (Class<?> i : TestContainer.class.getDeclaredClasses()) {
			mappedResults.put(i.getName(), parser.parseClass(i, null));
		}
	}

	/**
	 * Tests if <tt>AnnotationParser</tt> throws an exception if a class wishes
	 * to be handled by an <tt>AnnotationIR</tt>.
	 */
	@Test(expected = RuntimeException.class)
	public void testRejectAnnotationIRClass() {
		parser.parseClass(DeclareAnnotationIR.class, null);
	}

	/**
	 * Tests if <tt>AnnotationParser</tt> throws an exception if a class wishes
	 * to be handled by an <tt>MemberDataIR</tt>.
	 */
	@Test(expected = RuntimeException.class)
	public void testRejectMemberIRClass() {
		parser.parseClass(DeclareMemberCollectingIR.class, null);
	}

	/**
	 * Tests if <tt>AnnotationParser</tt> throws an exception if a class does
	 * not inherit from <tt>AnnotationIR</tt> at all.
	 */
	@Test(expected = RuntimeException.class)
	public void testRejectObjectIRClass() {
		parser.parseClass(DeclareObject.class, null);
	}

	/**
	 * Checks if there was an IR with the class and test class name provided
	 * added to the results.
	 * 
	 * @param resultName
	 *            name of the parsed test class
	 * @param irClass
	 *            IR class to match
	 */
	public void checkIRClass(String resultName, Class<?> irClass) {
		ClassIR ir = mappedResults.get(resultName);
		Assert.assertNotNull(ir);
		Assert.assertEquals(irClass, ir.getClass());
	}

	/**
	 * Checks if the classIR mapped to the provided test class name does not
	 * contain any items.
	 * 
	 * @param resultName
	 *            name of the parsed test class
	 */
	public void checkIREmpty(String resultName) {
		ClassIR ir = mappedResults.get(resultName);
		Assert.assertTrue(ir.collectedAnnotations.isEmpty());
		Assert.assertTrue(ir.collectTypes.isEmpty());
		for (Collection<MemberIR> i : ir.memberIRs.values()) {
			Assert.assertTrue(i.isEmpty());
		}
	}

	/**
	 * Tests if the default IR class is used if a class doesn't define one.
	 */
	@Test
	public void testNoIRClass() {
		String s = DeclareNoIR.class.getName();
		checkIRClass(s, ClassIR.class);
		checkIREmpty(s);
	}

	/**
	 * Tests if the default IR class is used if the class defines it.
	 */
	@Test
	public void testDefaultIRClass() {
		String s = DeclareDefaultIR.class.getName();
		checkIRClass(s, ClassIR.class);
		checkIREmpty(s);
	}

	/**
	 * Tests if using an IR class that does not support being the first IR
	 * instantiated for a parsed class with <tt>AnnotationOverride</tt> is still
	 * possible if it is not used as default
	 */
	@Test
	public void testInvalidAnnotationOverride() {
		parser.parseClass(DeclareInvalidFirstOverride.class, null);
	}

	/**
	 * Tests if using an IR class that does not support being the first IR
	 * instantiated for a parsed class normally is still possible when not using
	 * <tt>AnnotationOverride</tt>
	 */
	@Test
	public void testInvalidFirstInstantiateNormally() {
		ClassIR cir = parser.parseClass(DeclareInvalidFirstNoOverride.class,
				null);
		Assert.assertNotNull(cir);
		Assert.assertEquals(InvalidFirstExtendingIR.class, cir.getClass());
	}

	/**
	 * Tests if using an IR class that can only be used as default after an
	 * <tt>AnnotationOverride</tt> will provoke an exception.
	 */
	@Test(expected = RuntimeException.class)
	public void testInvalidNormalOverride() {
		parser.parseClass(DeclareInvalidNormalOverride.class, null);
	}

	/**
	 * Tests if using an IR class that can only be used as default like a
	 * normal, valid IR class will provoke an exception.
	 */
	@Test(expected = RuntimeException.class)
	public void testInvalidNormalNoOverride() {
		parser.parseClass(DeclareInvalidNormalNoOverride.class, null);
	}

	/**
	 * Tests if an IR class extending the default will be used if the class
	 * defines it
	 */
	@Test
	public void testExtendingIRClass() {
		String s = DeclareExtendingIR.class.getName();
		checkIRClass(s, ExtendingClassIR.class);
		checkIREmpty(s);
	}

	/**
	 * Tests if <tt>AnnotationOverride</tt> does not have any effect on default
	 * IR class usage.
	 */
	@Test
	public void testAnnotationOverrideCompatible() {
		String s = DeclareOverride.class.getName();
		checkIRClass(s, ClassIR.class);
		checkIREmpty(s);
	}

	/**
	 * Tests if <tt>AnnotationOverride</tt> does not have any effect on
	 * extending IRs on the same class level
	 */
	@Test
	public void testOverrideAndExtend() {
		String s = DeclareOverrideAndExtendingIR.class.getName();
		checkIRClass(s, ExtendingClassIR.class);
		checkIREmpty(s);
	}
}
